【JavaScript】ループ処理をマスターしよう

您所在的位置:网站首页 js object this 【JavaScript】ループ処理をマスターしよう

【JavaScript】ループ処理をマスターしよう

#【JavaScript】ループ処理をマスターしよう| 来源: 网络整理| 查看: 265

はじめに

プログラミング始めたての頃、JavaScript の書籍とか読んでるとこんなこと思いませんか?

「JavaScript ってループのやり方多すぎてどこで何を使えばいいかよくわからん」

かく言う僕もついこの前まではとりあえず生!のノリで forEach を乱用しまくり野郎だった頃が記憶に新しいです。

今回はそんな方に向けて状況によって適切なループ処理を選択できるようになるための、いちアプローチになればと思いこの記事を書きました。

参考になれば幸いです 🙏

ループ処理の選択基準

まず、以下 3 つの分類で扱うループを振り分けるのが良いと思います。

シンプルに配列やオブジェクトをループしたいとき ループの要素ごとに条件を設け配列を加工したいとき ループの要素ごとに真偽値を取得したいとき

3 つの大カテゴリーで分けることができましたら以下のカテゴリーからループを選択します。

シンプルに配列やオブジェクトをループしたいとき while / do...while for for...of for...in Array.forEach ループの要素ごとに条件を設け配列を加工したいとき Array.map Array.filter Array.reduce Array.find ループの要素に対して真偽値を取得したいとき Array.every Array.some

こちらをベースにそれぞれの動きや特徴を確認していきます。

while / do...while

whileとdo...whileは条件式が true である間ループを繰り返し、条件式が false、または break したときに処理を終了します。

以下のサンプルをご確認ください。

let i = 0; while (i { if (index ${element}`); }); console.log(array); /* index 0 番目の値 => a index 1 番目の値 => b [ 'a', 'b' ] */ // すでに参照された要素を削除した場合次の要素は飛ばされる const array = ["a", "b", "c"]; array.forEach((element, index, self) => { console.log(`index ${index} 番目の値 => ${element}`); if (index a index 1 番目の値 => c [ 'b', 'c' ] */

1 つ目のサンプルは要素を追加した場合は Array.forEach の実行時には参照されないが、元の配列には要素が追加されていることが確認できます。

2 つ目のサンプルは Array.forEach の実行した最初に配列の最後の要素を削除しています。削除した要素は参照しないことが確認できます。

3つ目のサンプルは 1 つ目の要素を処理後に処理済みの 1 つ目の要素を削除しています。すると次の 2 番目の要素が参照されず 3 つ目の要素が参照されていることが確認できます。

これは配列の要素を削除したことで要素のインデックスが左づめになって 1 ずつずれたことによる動きです。

this の使用

Array.forEach の callback に this の値を渡すサンプルです。以下のサンプルをご確認ください。

function Counter() { this.sum = 0; this.count = 0; } Counter.prototype.add = function (array) { array.forEach(function (entry) { this.sum += entry; ++this.count; }, this); }; const obj = new Counter(); obj.add([2, 5, 9]); console.log(obj.count); console.log(obj.sum); /* 3 16 */

Counterというオブジェクトを生成し、prototype にaddという forEach の関数の機能を拡張した後にメソッドを呼び出してます。

実行結果で this の値を参照してそれぞれ値が変更されることが確認できます。

もちろん callback で this を渡さなければ参照できません。

function Counter() { this.sum = 0; this.count = 0; } Counter.prototype.add = function (array) { array.forEach(function (entry) { this.sum += entry; ++this.count; }); }; const obj = new Counter(); obj.add([2, 5, 9]); console.log(obj.count); console.log(obj.sum); /* 0 0 */

ですが、callback の記述をES6で追加されたアロー関数に変更すると this を省略しても参照できます。

function Counter() { this.sum = 0; this.count = 0; } Counter.prototype.add = function (array) { array.forEach((entry) => { this.sum += entry; ++this.count; }); }; const obj = new Counter(); obj.add([2, 5, 9]); console.log(obj.count); console.log(obj.sum); /* 3 16 */

もちろん省略しないで this を明示的に渡しても動作します。

省略記法

Array 系のメソッドの良さの 1 つはアロー関数と組み合わせることで、省略して 1 行でコードを書けるスマートさです。

以下のサンプルをご確認ください。

// 基本系 array.forEach((element, index) => { console.log(`index ${index} の値 ${element}`); }); // 省略記法1:{}を省略 array.forEach((element, index) => console.log(`index ${index} の値 ${element}`) ); // 省略記法2:引数が1つなら()も省略できる array.forEach((element) => console.log(element));

コメント通りですが、基本波かっこ{}は省略可能です。また、引数が 1 つだけの場合は()も省略できるのでよりスッキリしますね。

ただ、3 つ目の書き方は人によって書く人、書かない人で表記がブレる原因になりますのでコード規約などで定義しておく方が良いと思います。

prettier で、コーディング中は各々好きな書き方しても最終的には整えてくれる。みたいな仕組みを導入しておくなども良いと思います。

break と continue は使えない

配列操作として Array.forEach はfor より手軽にスマートにかける反面、弱点としては break と continue ができないという欠点があります。

以下のサンプルのように return を使うことで continue のような動作を表現することは可能です。

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9]; array.forEach((element, index) => { if ((index + 1) % 3 !== 0) return; console.log(`${index + 1}は3の倍数です`); }); /* 3は3の倍数です 6は3の倍数です 9は3の倍数です */

ただ break は仕様上どう頑張ってもできません。ネットなどで検索すればArray.someとの組み合わせで break と同じ動きをさせるなどの記事もありますが、推奨できるものではないのでここでは書きません。

言ってしまえば return で continue する書き方もあまり推奨できません。この場合だと後述しますArray.filterで実装するのが適切です。

冒頭で break と continue が使えないことが弱点と言いましたが、代わりになるメソッドはありますので Array.forEach はあくまですでに加工が終了した配列をシンプルにループする時に使うメソッドと割り切ってその他は別の方法でやるという風に実装していくのが良いかと思います。

Array.map

Array.mapは配列の各要素に対し、順番にメソッド内に記述した callback の結果を返します。

callback に与えられる引数は以下です。

第 1 引数:配列 n 番目の要素の値 第 2 引数:第 1 引数の index(省略可) 第 3 引数:元の配列(省略可)

返り値はcallback の結果から作成される新しい配列です。

動作を確認していきます。以下、サンプルを確認ください。

const array = [1, 2, 3]; const newArray = array.map((element, index, self) => { return element * 2; }); console.log(array); // [ 1, 2, 3 ] console.log(newArray); // [ 2, 4, 6 ]

元の配列から各要素を 2 倍にした新しい配列が生成されていることを確認できます。

また、配列やオブジェクトは参照型ですのでそのまま代入すると参照渡しになりますが、map は新しい配列として生成されるので参照渡しの影響を受けません。

以下のサンプルを確認ください。

const array = [1, 2, 3]; const copy = array; const newArray = array.map((element, index, self) => { //if(index < 1) array.splice(2, 1); return element * 2; }); array[0] = 999; console.log(array); // [ 999, 2, 3 ] // copyはarrayをそのまま代入したので参照渡し console.log(copy); // [ 999, 2, 3 ] // newArrayはmapで生成されたので影響を受けない console.log(newArray); // [ 2, 4, 6 ]

このように map の使い所は callback から返された全く新しい配列を生成したいという時に使うのが適しています。

逆に返された配列を使用しない、callback から値を返さない場面などでは適していません。

省略記法も Array.forEach と同様の書き方ができます。違う点があるとしたら map の場合は値を返す必要があるので return の扱いですが、こちらは必ず省略してください。

サンプルで動作を確認します。

const array = [1, 2, 3]; const newArray = array.map(element => element * 2); console.log(newArray); // [ 2, 4, 6 ] // returnを書くとSyntaxErrorになる const newArray = array.map(element => return element * 2); // SyntaxError: Unexpected token 'return'

return を書くとエラーになることが確認できます。

また、callback に this の変数を渡すこともできます。この時の挙動は Array.forEach と同様です。

要素を追加・削除したとき

Array.forEach 同様に map が呼び出された後に追加された要素に対して callback は実行されず、削除された要素に対しても callback は実行されません。

サンプルで動作を確認します。

// サンプル1 const array = [1, 2, 3]; const newArray = array.map((element, index) => { if (index { if (index v.title).forEach((element) => console.log(element)); /* 呪術廻戦 ONE PIECE 鬼滅の刃 */

配列の中にジャンプの作品名と作者名のオブジェクトが格納されており Array.map で作品名のみ抽出します。

Array.map は新しい配列を返しますのでそのまま配列操作系のメソッドをドット(.)で繋ぐメソッドチェーンにより連続して使用することができます。

このメソッドチェーンは後述します他のArray.filterやArray.reduceなどでも使用することができます。

ただ、Array.forEach に関しては戻り値が undefined のためメソッドチェーンの最後に持ってくることはできても**Array.forEach().map()**のような書き方はできませんので注意してください。

また、サンプルのような配列オブジェクトをループするときは obj.property のようにしてアクセスしますが、分割代入を組み合わせるとより処理の意図を伝えやすくなります。

const jumpComics = [ { title: "呪術廻戦", author: "芥見下々" }, { title: "ONE PIECE", author: "尾田栄一郎" }, { title: "鬼滅の刃", author: "吾峠呼世晴" }, ]; jumpComics.map(({ title }) => title).forEach((element) => console.log(element)); /* 呪術廻戦 ONE PIECE 鬼滅の刃 */

少し余談ですが、React なども一緒に勉強したいと思ってる方、この分割代入はめっちゃ使うので合わせてチェックするのがおすすめです。

Array.filter

Array.filterは配列の各要素に対し、順番にメソッド内に記述した callback を実行し、条件に一致した結果を返します。

callback に与えられる引数は以下です。

第 1 引数:配列 n 番目の要素の値 第 2 引数:第 1 引数の index(省略可) 第 3 引数:元の配列(省略可)

返り値は条件に一致した要素からなる新しい配列です。

this 変数も Array.map などと同様に callback に渡せます。

動作を確認していきます。以下、サンプルを確認ください。

const array = ["apple", "banana", "grapes", "mango", "orange"]; const newArray = array.filter((element) => { return element.indexOf("ap") !== -1; }); console.log(newArray); // ['apple', 'grapes'] // mapなどと同様省略記法でもOKです const newArray = array.filter((element) => element.indexOf("ap") !== -1); console.log(newArray);

String.indexOfを使用しapの文字列を含む値のみを返すという条件を与えています。

このようにサンプルとそのメソッドの名前からイメージのつく通り Array.filter は配列に対してフィルタをかけた結果を取得したい時などに最適です。

また、Array.filter は条件に一致した要素を配列で返すという仕様です。条件に一致するということはすなわち真偽値で返しても同様の結果を得ることができます。

以下、その一例をサンプルとして載せます。

const array = ["apple", "banana", "grapes", "mango", "orange"]; const newArray = array.filter((element) => { if (element.indexOf("ap") !== -1) { return true; } return false; }); console.log(newArray); // ['apple', 'grapes']

一見true / falseの配列を生成しているように見えますが Array.filter では true を返せば callback 実行中の要素を返し、false ではスキップするという動きになります。

要素を追加、削除した時のサンプルも確認してみます。

// 要素の追加 const array = [1, 2, 3]; const newArray = array.filter((element, index) => { if (index console.log(element)); /* 呪術廻戦の作者は芥見下々 ONE PIECEの作者は尾田栄一郎 鬼滅の刃の作者は吾峠呼世晴 */

Array.filter で少年ジャンプ連載のマンガのみにフィルタしてから Array.map で作品名+作者の新しい配列を生成し、Array.forEach で結果を出力しています。

Array.reduce

Array.reduceは配列の各要素に対し、順番にメソッド内に記述した callback を実行します。その際に直前の要素が返されその要素を元に処理した結果が最終値になります。

callback に与えられる引数は以下です。

第 1 引数:直前の要素の結果 第 2 引数:配列 n 番目の要素の値 第 3 引数:第 1 引数の index(省略可) 第 4 引数:元の配列(省略可)

返り値は配列全体に実行された callback の結果です。

Array.reduce は Array.map や Array.filter などでいう this の変数を渡す場所でコールバックの初期値を任意で渡すことができます。

この初期値を渡したかどうかで結果が異なるのでそれぞれの結果を見ていきます。

callback に初期値を渡さない場合

Array.reduce は callback の第 1 引数が直前の要素の結果となります。

それでは配列 0 番目というまだ直前の要素が存在しない場合ではどのようなふるまいになるのでしょうか。

以下のサンプルで動作を確認ください。

const array = [1, 2, 3, 4]; const newArray = array.reduce((pre, current, index, self) => { return current; }); // 4 // 省略記法でもokです const newArray = array.reduce((pre, current, index, self) => current);

ただ単に callbalck 実行中の配列の要素を返すだけのサンプルで結果は 4 です。

4 つの引数を全部 console で出力した時の結果をまとめて見ましょう。

pre current index self return 1 2 1 [ 1, 2, 3, 4 ] 2 2 3 2 [ 1, 2, 3, 4 ] 3 3 4 3 [ 1, 2, 3, 4 ] 4

4 つの要素からなる配列なのに current 値は 2 でループの回数は 3 回、index も 1 からスタートと見た感じ最初の要素がスキップされた挙動に見えます。

pre には最初の要素の値である 1 が入っています。

以上から初期値が指定されなかった場合配列の最初の要素で初期化され、current が 2 番目の要素からはじまるということが確認できます。

次に最初の説明でもふれました直前の要素を返すとはどういうことでしょうか。

以下のサンプルを確認ください。

const array = [1, 2, 3, 4]; const newArray = array.reduce((pre, current, index, self) => { return pre + current; }); // 10 // 省略記法でもokです const newArray = array.reduce((pre, current, index, self) => current);

直前の結果(pre)と現在の要素(current)を足した結果を返すサンプルで結果は 10 です。

先ほどと同様に 4 つの引数を全部 console で出力した時の結果をまとめて見ましょう。

pre current index self return 1 2 1 [ 1, 2, 3, 4 ] 3 3 3 2 [ 1, 2, 3, 4 ] 6 6 4 3 [ 1, 2, 3, 4 ] 10

pre の値が加算されていますね。加算した値は直前の要素の pre と current を足した return の結果です。直前の要素を返すというのは動きのことを指します。

callback に初期値を渡す場合

次に callback に初期値を渡した場合の挙動です。

まずはサンプルで動作と構文を確認ください。

const array = [1, 2, 3, 4]; const newArray = array.reduce((pre, current, index, self) => { return pre + current; }, 10); console.log(newArray); // 20 // 省略記法でもかけます const newArray = array.reduce((pre, current, index, self) => pre + current, 10);

callback の後ろに 10 の値を渡してます。この値が初期値になり出力結果は 20 になります。こちらも同様に結果を整理していきましょう。

pre current index self return 10 1 0 [ 1, 2, 3, 4 ] 11 11 2 1 [ 1, 2, 3, 4 ] 13 13 3 2 [ 1, 2, 3, 4 ] 16 16 4 3 [ 1, 2, 3, 4 ] 20

ご覧の通り初期値を渡すことにより渡された値で初期化され配列の 0 番目から順番に実行されるようになります。

また、初期値で渡す値は数値である必要はありません。この初期値に渡す値によっていろいろな使い方ができるようになります。

一例をサンプルで確認してみます。

使用例:配列の重複をなくす

Array.reduce を使った一例として配列の重複をなくすというコードを書いてみます。

まずは Array.map を使った失敗する例を見てみます。

const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.map((elem, index, self) => { if (self.indexOf(elem) === index) { return elem; } }); console.log(newArray); // [ 'apple', 'orange', 'peach', 'banana', undefined, undefined ]

重複の値は検知できますが undefined が配列に入ってきてしまいます。

それでは、Array.reduce での例を見てみます。

const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.reduce((prev, current) => { if (!prev.includes(current)) { prev.push(current); } return prev; }, []); console.log(newArray); // [ 'apple', 'orange', 'peach', 'banana' ]

初期値に空の配列を渡すのと、Array.reduce の直前の結果が返るという性質を利用して直前の配列の中に現在の要素の値が存在しなかった場合は配列に追加するという処理を行っています。

とは言ってもこの例だと Array.filter でも実現できるんですよね。

const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.filter( (elem, index, self) => self.indexOf(elem) === index ); console.log(newArray);

Array.filter の真偽値を返せば要素の値が返る性質を利用してます。省略記法でも書けてこっちのがスマートですね。

Array.reduce は始めはとっつき難いかもしれません。 ただ慣れるとめっちゃ便利で、Array.reduce 使えば大体の配列処理ができてしまいます。

ただ、便利だからってなんでもかんでも使うというのはおすすめではないです。 あくまで、やりたいことに一番近しい名前と機能を持っているメソッドを選択しコードの可読性は可能な限り高くする努力はしましょう。

Array.find

Array.findは配列の各要素に対し、順番にメソッド内に記述した callback を実行し、条件に一致した最初の要素の結果を返します。

callback に与えられる引数は以下です。

第 1 引数:配列 n 番目の要素の値 第 2 引数:第 1 引数の index(省略可) 第 3 引数:元の配列(省略可)

返り値は条件に一致した最初の要素です。条件に一致しなかった場合はundefinedが返ります。

this 変数もここまでと同様に callback に渡せます。

サンプルで動作を確認していきます。

const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.find((elem) => { return elem === "banana"; }); console.log(newArray); // banana // 省略記法でもOKです const newArray = array.find((elem) => elem === "banana"); // 配列の要素内に条件値が存在しない場合はundefinedを返す const newArray = array.find((elem) => elem === "tomato"); console.log(newArray); // undefined

このように Array.find は言葉から察しがつくように配列内の要素の値を探し、その結果を取得したいというときに最適です。

配列内の要素の値ではなくインデックスを取得したい場合はArray.findIndexを使用します。

その他にも配列内の指定した値のインデックスを取得するArray.indexOfや配列内に要素が存在するか真偽値を取得するArray.includesなどがありますが、これらは配列の検索というカテゴリで改めてどこかでまとめようかなと思います。

話を戻します。Array.find は Array.filter と同様に条件の一致を見ますので真偽値を返すことでも対象の要素を取得することができます。

サンプルを確認ください。

const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.find((elem) => { if (elem === "banana") { return true; } return false; }); console.log(newArray); // banana

要素が banana の時に true を返していますが出力結果は banana であることを確認できます。

メソッドが呼び出された時に配列に要素を追加・削除した場合の動きも確認しておきます。

// 配列に要素を追加 const array = ["apple", "orange", "peach", "banana", "apple", "banana"]; const newArray = array.find((elem, index) => { if (index { if (index { return elem elem elem); console.log(newArray); // ?????

答えは false を返します。

これは JavaScript では 0 は false とみなすという仕様のためです。その他にも値のみで false と判定するリテラル値は複数ありますので簡単にリスト化しておきます。

リテラル 判定結果 {} true "文字列" true "" false 1 true -1 true 0 false [] true true true false false undefined false null false NaN false

以上をふまえ次のサンプルを確認ください。

const array = [0, "", undefined, null]; const newArray = array.every((elem) => elem); console.log(newArray); // false // 反転すればtrueになる const newArray = array.every((elem) => !elem); console.log(newArray); // true

リストを元に false と判定するリテラルのみで構成した配列で検証した結果 false を返し、エクスクラメーション(!)で否定すれば true が返るサンプルです。

この特性を生かすことで配列の正当性などを検証するのに Array.every は最適です。

また、判定式のリストはそのまま if の条件などにも当てはまりますので覚えておくと良いと思います。

次に配列を追加・削除した場合のサンプルを確認していきましょう。以下をご確認ください。

// 配列に要素追加 const array = [0, 1, 2, 3, 4, 5]; const newArray = array.every((elem, index) => { if (index elem > 5); // false判定ではない要素が1つでも存在するか const array = [0, "", undefined, null, "string"]; console.log(newArray); // true const array = [0, "", undefined, null]; console.log(newArray); // false // 要素を追加 const array = [0, 1, 2, 3, 4, 5]; const newArray = array.some((elem, index) => { if (index 5; }); console.log(newArray); // false // 要素の削除 const array = [0, 1, 2, 3, 4, 5, 99]; const newArray = array.some((elem, index) => { if (index 5; }); console.log(newArray); // false

動きは同じですのでArray.every は全要素が条件一致、Array.some はどれかしらの要素が条件一致ということを頭に入れておきましょう。

さいごに

さいごまで読んでいただきありがとうございます。

まだまだ配列系のメソッドはありますが、よく使うものとしては大体網羅できたかと思います。 ちょっとしたクイックリファレンスみたいに活用してもらえたりしたら嬉しいです。

余談ですが、プロジェクトによっては forEach や for の使用は禁止。基本繰り返し処理は map を使いましょう。みたいなコーディング規約があったりします。

個人的には記事内でちょくちょく言及してますが、何をしたいかによって、その名前や機能から一番可読性が高くなる方法を選ぶのがベストであると思うので反対派です。

コード規約ならそれに沿うが最適解ではあるのですが、何でもかんでもモダンな Array メソッドを使えばいいってことでもないというのはスタンスとして持っておくと良いのではないかなと思い書いた余談でした。

間違いの指摘やリクエストなどありましたら加筆していきたので是非、ご意見をいただけたらと思います。

それではまた次の記事でお会いしましょう。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3